package com.talkweb.osmharmony.views.overlay.mylocation;

import com.talkweb.osmharmony.ResourceTable;
import com.talkweb.osmharmony.api.IMapController;
import com.talkweb.osmharmony.api.IMapView;
import com.talkweb.osmharmony.config.Configuration;
import com.talkweb.osmharmony.util.*;
import com.talkweb.osmharmony.views.MapView;
import com.talkweb.osmharmony.views.Projection;
import com.talkweb.osmharmony.views.overlay.Overlay;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.location.Location;
import ohos.media.image.PixelMap;
import ohos.multimodalinput.event.TouchEvent;

import java.util.LinkedList;

/**
 * @author Marc Kurtz
 * @author Manuel Stahl
 */
public class MyLocationNewOverlay extends Overlay implements IMyLocationConsumer, Overlay.Snappable {

    // ===========================================================
    // Constants
    // ===========================================================

    // ===========================================================
    // Fields
    // ===========================================================

    protected Paint mPaint = new Paint();
    protected Paint mCirclePaint = new Paint();

    protected final float mScale;

    protected PixelMap mPersonBitmap;
    protected PixelMap mDirectionArrowBitmap;

    protected MapView mMapView;

    private IMapController mMapController;
    public IMyLocationProvider mMyLocationProvider;

    private final LinkedList<Runnable> mRunOnFirstFix = new LinkedList<Runnable>();
    private final Point mDrawPixel = new Point();
    private final Point mSnapPixel = new Point();
    private EventHandler mHandler;

    /**
     * if true, when the user pans the map, follow my location will automatically disable
     * if false, when the user pans the map, the map will continue to follow current location
     */
    protected boolean enableAutoStop = true;
    private Location mLocation;
    private final GeoPoint mGeoPoint = new GeoPoint(0, 0); // for reuse
    private boolean mIsLocationEnabled = false;
    protected boolean mIsFollowing = false; // follow location updates
    protected boolean mDrawAccuracyEnabled = true;

    /**
     * Coordinates the feet of the person are located scaled for display density.
     */
    protected final Point mPersonHotspot;

    protected float mDirectionArrowCenterX;
    protected float mDirectionArrowCenterY;

    private boolean wasEnabledOnPause = false;
    // ===========================================================
    // Constructors
    // ===========================================================

    public MyLocationNewOverlay(MapView mapView) {
        this(new GpsMyLocationProvider(mapView.getContext()), mapView);
    }

    public MyLocationNewOverlay(IMyLocationProvider myLocationProvider, MapView mapView) {
        super();
        mScale = DisplayUtils.getDisplayAttributes(mapView.getContext()).densityPixels;
        mMapView = mapView;
        mMapController = mapView.getController();
        mCirclePaint.setColor(new Color(Color.argb(0, 100, 100, 255)));
        mCirclePaint.setAntiAlias(true);
        mPaint.setFilterBitmap(true);

        setDirectionArrow(ResourceHelper.getPixmap(mapView.getContext(),ResourceTable.Media_person),
                ResourceHelper.getPixmap(mapView.getContext(),ResourceTable.Media_round_navigation_white_48));

        // Calculate position of person icon's feet, scaled to screen density
        mPersonHotspot = new Point(24.0f * mScale + 0.5f, 39.0f * mScale + 0.5f);

        mHandler = new EventHandler(EventRunner.getMainEventRunner());
        setMyLocationProvider(myLocationProvider);
    }

    /**
     * fix for https://github.com/osmdroid/osmdroid/issues/249
     *
     * @param personBitmap
     * @param directionArrowBitmap
     */
    public void setDirectionArrow(final PixelMap personBitmap, final PixelMap directionArrowBitmap) {
        this.mPersonBitmap = personBitmap;
        this.mDirectionArrowBitmap = directionArrowBitmap;

        mDirectionArrowCenterX = mDirectionArrowBitmap.getImageInfo().size.width / 2.0f - 0.5f;
        mDirectionArrowCenterY = mDirectionArrowBitmap.getImageInfo().size.height / 2.0f - 0.5f;

    }

    @Override
    public void onResume() {
        super.onResume();
        if (wasEnabledOnPause)
            this.enableFollowLocation();
        this.enableMyLocation();
    }

    @Override
    public void onPause() {
        wasEnabledOnPause = mIsFollowing;
        this.disableMyLocation();
        super.onPause();
    }

    @Override
    public void onDetach(MapView mapView) {
        this.disableMyLocation();
		if (mPersonBitmap != null) {
			mPersonBitmap.release();
            mPersonBitmap = null;
		}
		if (mDirectionArrowBitmap != null) {
			mDirectionArrowBitmap.release();
            mDirectionArrowBitmap = null;
		}
        this.mMapView = null;
        this.mMapController = null;
        mHandler = null;
        mCirclePaint = null;
        mLocation = null;
        if (mMyLocationProvider != null)
            mMyLocationProvider.destroy();

        mMyLocationProvider = null;
        super.onDetach(mapView);
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    /**
     * If enabled, an accuracy circle will be drawn around your current position.
     *
     * @param drawAccuracyEnabled whether the accuracy circle will be enabled
     */
    public void setDrawAccuracyEnabled(final boolean drawAccuracyEnabled) {
        mDrawAccuracyEnabled = drawAccuracyEnabled;
    }

    /**
     * If enabled, an accuracy circle will be drawn around your current position.
     *
     * @return true if enabled, false otherwise
     */
    public boolean isDrawAccuracyEnabled() {
        return mDrawAccuracyEnabled;
    }

    public IMyLocationProvider getMyLocationProvider() {
        return mMyLocationProvider;
    }

    protected void setMyLocationProvider(IMyLocationProvider myLocationProvider) {
        if (myLocationProvider == null)
            throw new RuntimeException(
                    "You must pass an IMyLocationProvider to setMyLocationProvider()");

        if (isMyLocationEnabled())
            stopLocationProvider();

        mMyLocationProvider = myLocationProvider;
    }

    public void setPersonHotspot(float x, float y) {
        mPersonHotspot.modify(x, y);
    }

    protected void drawMyLocation(final Canvas canvas, final Projection pj, final Location lastFix) {
        pj.toPixels(mGeoPoint, mDrawPixel);

        if (mDrawAccuracyEnabled) {
            final float radius = lastFix.getAccuracy()
                    / (float) TileSystem.GroundResolution(lastFix.getLatitude(),
                    pj.getZoomLevel());

            mCirclePaint.setAlpha(50);
            mCirclePaint.setStyle(Paint.Style.FILL_STYLE);
            canvas.drawCircle(mDrawPixel.getPointX(), mDrawPixel.getPointY(), radius, mCirclePaint);

            mCirclePaint.setAlpha(150);
            mCirclePaint.setStyle(Paint.Style.STROKE_STYLE);
            canvas.drawCircle(mDrawPixel.getPointX(), mDrawPixel.getPointY(), radius, mCirclePaint);
        }


        if (lastFix.getDirection() != 0) {
            canvas.save();
            // Rotate the icon if we have a GPS fix, take into account if the map is already rotated
            float mapRotation;
            mapRotation = (float) lastFix.getDirection();
            if (mapRotation >= 360.0f)
                mapRotation = mapRotation - 360f;
            canvas.rotate(mapRotation, mDrawPixel.getPointX(), mDrawPixel.getPointY());
            // Draw the bitmap
            canvas.drawPixelMapHolder(new PixelMapHolder(mDirectionArrowBitmap), mDrawPixel.getPointX()
                            - mDirectionArrowCenterX, mDrawPixel.getPointY() - mDirectionArrowCenterY,
                    mPaint);
            canvas.restore();
        } else {
            canvas.save();
            // Unrotate the icon if the maps are rotated so the little man stays upright
            canvas.rotate(-mMapView.getMapOrientation(), mDrawPixel.getPointX(),
                    mDrawPixel.getPointY());
            // Draw the bitmap
            canvas.drawPixelMapHolder(new PixelMapHolder(mPersonBitmap), mDrawPixel.getPointX() - mPersonHotspot.getPointX(),
                    mDrawPixel.getPointY() - mPersonHotspot.getPointY(), mPaint);
            canvas.restore();
        }
    }

    // ===========================================================
    // Methods from SuperClass/Interfaces
    // ===========================================================

    @Override
    public void draw(Canvas c, Projection pProjection) {
        if (mLocation != null && isMyLocationEnabled()) {
            drawMyLocation(c, pProjection, mLocation);
        }
    }

    @Override
    public boolean onSnapToItem(final int x, final int y, final Point snapPoint,
                                final IMapView mapView) {
        if (this.mLocation != null) {
            Projection pj = mMapView.getProjection();
            pj.toPixels(mGeoPoint, mSnapPixel);
            snapPoint.modify(mSnapPixel.getPointX(), mSnapPixel.getPointY());
            final double xDiff = x - mSnapPixel.getPointX();
            final double yDiff = y - mSnapPixel.getPointY();
            boolean snap = xDiff * xDiff + yDiff * yDiff < 64;
            if (Configuration.getInstance().isDebugMode()) {
                Log.d(IMapView.LOGTAG, "snap=" + snap);
            }
            return snap;
        } else {
            return false;
        }
    }

    public void setEnableAutoStop(boolean value) {
        this.enableAutoStop = value;
    }

    public boolean getEnableAutoStop() {
        return this.enableAutoStop;
    }

    @Override
    public boolean onTouchEvent(final TouchEvent event, final MapView mapView) {
        final boolean isSingleFingerDrag = (event.getAction() == TouchEvent.POINT_MOVE)
                && (event.getPointerCount() == 1);

        if (event.getAction() == TouchEvent.PRIMARY_POINT_DOWN && enableAutoStop) {
            this.disableFollowLocation();
        } else if (isSingleFingerDrag && isFollowLocationEnabled()) {
            return true;  // prevent the pan
        }

        return super.onTouchEvent(event, mapView);
    }

    // ===========================================================
    // Methods
    // ===========================================================

    /**
     * Return a GeoPoint of the last known location, or null if not known.
     */
    public GeoPoint getMyLocation() {
        if (mLocation == null) {
            return null;
        } else {
            return new GeoPoint(mLocation);
        }
    }

    public Location getLastFix() {
        return mLocation;
    }

    /**
     * Enables "follow" functionality. The map will center on your current location and
     * automatically scroll as you move. Scrolling the map in the UI will disable.
     */
    public void enableFollowLocation() {
        mIsFollowing = true;

        // set initial location when enabled
        if (isMyLocationEnabled()) {
            Location location = mMyLocationProvider.getLastKnownLocation();
            if (location != null) {
                setLocation(location);
            }
        }

        // Update the screen to see changes take effect
        if (mMapView != null) {
            mMapView.postLayout();
        }
    }

    /**
     * Disables "follow" functionality.
     */
    public void disableFollowLocation() {
        mMapController.stopAnimation(false);
        mIsFollowing = false;
    }

    /**
     * If enabled, the map will center on your current location and automatically scroll as you
     * move. Scrolling the map in the UI will disable.
     *
     * @return true if enabled, false otherwise
     */
    public boolean isFollowLocationEnabled() {
        return mIsFollowing;
    }

    @Override
    public void onLocationChanged(final Location location, IMyLocationProvider source) {

        if (location != null && mHandler != null) {
            // These location updates can come in from different threads
            mHandler.postTimingTask(new Runnable() {
                @Override
                public void run() {
                    setLocation(location);

                    for (final Runnable runnable : mRunOnFirstFix) {
                        Thread t = new Thread(runnable);
                        t.setName(this.getClass().getName() + "#onLocationChanged");
                        t.start();
                    }
                    mRunOnFirstFix.clear();
                }
            }, 0);
        }
    }

    protected void setLocation(Location location) {
        mLocation = location;
        mGeoPoint.setCoords(mLocation.getLatitude(), mLocation.getLongitude());
        if (mIsFollowing) {
            mMapController.animateTo(mGeoPoint);
        } else if (mMapView != null) {
            mMapView.postLayout();
        }
    }

    public boolean enableMyLocation(IMyLocationProvider myLocationProvider) {
        // Set the location provider. This will call stopLocationProvider().
        setMyLocationProvider(myLocationProvider);

        boolean success = mMyLocationProvider.startLocationProvider(this);
        mIsLocationEnabled = success;

        // set initial location when enabled
        if (success) {
            Location location = mMyLocationProvider.getLastKnownLocation();
            if (location != null) {
                setLocation(location);
            }
        }

        // Update the screen to see changes take effect
        if (mMapView != null) {
            mMapView.postInvalidate();
        }

        return success;
    }

    /**
     * Enable receiving location updates from the provided IMyLocationProvider and show your
     * location on the maps. You will likely want to call enableMyLocation() from your Activity's
     * Activity.onResume() method, to enable the features of this overlay. Remember to call the
     * corresponding disableMyLocation() in your Activity's Activity.onPause() method to turn off
     * updates when in the background.
     */
    public boolean enableMyLocation() {
        return enableMyLocation(mMyLocationProvider);
    }

    /**
     * Disable location updates
     */
    public void disableMyLocation() {
        mIsLocationEnabled = false;

        stopLocationProvider();

        // Update the screen to see changes take effect
        if (mMapView != null) {
            mMapView.postLayout();
        }
    }

    protected void stopLocationProvider() {
        if (mMyLocationProvider != null) {
            mMyLocationProvider.stopLocationProvider();
        }
        if (mHandler != null)
            mHandler.removeAllEvent();
    }

    /**
     * If enabled, the map is receiving location updates and drawing your location on the map.
     *
     * @return true if enabled, false otherwise
     */
    public boolean isMyLocationEnabled() {
        return mIsLocationEnabled;
    }

    /**
     * Queues a runnable to be executed as soon as we have a location fix. If we already have a fix,
     * we'll execute the runnable immediately and return true. If not, we'll hang on to the runnable
     * and return false; as soon as we get a location fix, we'll run it in in a new thread.
     */
    public boolean runOnFirstFix(final Runnable runnable) {
        if (mMyLocationProvider != null && mLocation != null) {
            Thread t = new Thread(runnable);
            t.setName(this.getClass().getName() + "#runOnFirstFix");
            t.start();
            return true;
        } else {
            mRunOnFirstFix.addLast(runnable);
            return false;
        }
    }

    /**
     * enabls you to change the my location 'person' icon at runtime. note that the
     * hotspot is not updated with this method. see
     * {@link #setPersonHotspot}
     *
     * @param icon
     */
    public void setPersonIcon(PixelMap icon) {
        mPersonBitmap = icon;
    }
}
